/**
 * Title: jFamilyTree Text provider
 * Description: jFamilyTree Text provider
 * About: Designed and rewritten by Andreas Lundgren, based on the Treebolic application suite
 * Copyright: (c) 2001-2008
 * Terms of use:see license agreement at http://treebolic.sourceforge.net/en/license.htm
 * Author: Bernard Bou
 * Company: bsys
 */
package jFamilyTree.provider.text.indent;

import java.awt.Color;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.List;

import jFamilyTree.model.IEdge;
import jFamilyTree.model.INode;
import jFamilyTree.model.IProvider;
import jFamilyTree.model.IProviderContext;
import jFamilyTree.model.Model;
import jFamilyTree.model.MutableNode;
import jFamilyTree.model.Settings;
import jFamilyTree.model.Tree;
import jFamilyTree.model.Utils;

/**
 * Provider for file system
 * 
 * @author Bernard Bou
 */
public class Provider implements IProvider
{
	static private Color theBackgroundColor = Color.WHITE;

	/**
	 * Stack entry
	 */
	private class StackEntry
	{
		/**
		 * Node
		 */
		public MutableNode theNode;

		/**
		 * Node's level
		 */
		public int theLevel;

		/**
		 * Constructor
		 * 
		 * @param thisNode
		 *        node
		 * @param thisLevel
		 *        itslevel
		 */
		public StackEntry(MutableNode thisNode, int thisLevel)
		{
			theNode = thisNode;
			theLevel = thisLevel;
		}
	}

	/**
	 * Provider context
	 */
	private IProviderContext theContext;

	/**
	 * Constructor
	 */
	public Provider()
	{
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProvider#set(jFamilyTree.model.IProviderContext)
	 */
	public void connect(IProviderContext thisContext)
	{
		theContext = thisContext;
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProvider#makeModel(java.lang.String)
	 */
	public Model makeModel(String thisSource)
	{
		Tree thisTree = makeTree(thisSource, false);
		if (thisTree == null)
			return null;

		// settings
		boolean asTree = thisTree.getRoot().getChildren().size() < 3;
		Settings theseSettings = new Settings();
		theseSettings.theBackColor = theBackgroundColor;
		theseSettings.theNodeBackColor = Color.WHITE;
		theseSettings.theNodeForeColor = Color.BLACK;

		theseSettings.theOrientation = asTree ? "south" : "radial";
		theseSettings.theHasToolbarFlag = true;
		theseSettings.theBackColor = new Color(0xf5f5f0);
		theseSettings.theFontFace = "Dialog";
		theseSettings.theFontSize = 20;
		theseSettings.theFontSizeStep = 2;
		theseSettings.theExpansion = .9;
		theseSettings.theSweep = 1.2;
		theseSettings.theHasToolbarFlag = true;
		theseSettings.theHasStatusbarFlag = true;
		theseSettings.theFocusOnHoverFlag = true;
		theseSettings.theTreeEdgeColor = Color.GRAY;
		theseSettings.theTreeEdgeStyle = IEdge.SOLID | IEdge.TOTRIANGLE | IEdge.TOSOLID;
		return new Model(thisTree, theseSettings);
	}

	/*
	 * (non-Javadoc)
	 * @see jFamilyTree.model.IProvider#makeTree(java.lang.String, boolean)
	 */
	public Tree makeTree(String thisSource, boolean checkRecursion)
	{
		// get text file
		String thisTextFile = thisSource;
		if (thisTextFile == null)
			thisTextFile = theContext.getParameter("text");

		if (thisTextFile != null)
		{
			// URL
			URL thisURL = theContext.makeURL(thisTextFile);
			if (thisURL != null)
			{
				// parse
				theContext.putProgress("Loading ..." + thisTextFile, false);
				Tree thisTree = parseTree(thisURL);
				if (thisTree != null)
				{
					theContext.putProgress("Loaded " + thisTextFile, false);
					return thisTree;
				}
				else
					theContext.putMessage("Cannot load text file <" + thisTextFile + ">");
			}
			else
				theContext.putMessage("Bad URL " + thisTextFile);
		}
		return null;
	}

	/**
	 * Parse tree
	 * 
	 * @param thisUrl
	 *        text file URL
	 * @return tree
	 */
	private Tree parseTree(URL thisUrl)
	{
		// root
		MutableNode thisRootNode = new MutableNode(null, "root");
		thisRootNode.setLabel("root");
		thisRootNode.setBackColor(Color.RED);
		thisRootNode.setForeColor(Color.WHITE);

		Deque<StackEntry> thisStack = new ArrayDeque<StackEntry>();
		thisStack.push(new StackEntry(thisRootNode, -1));

		try
		{
			// input stream
			InputStream thisInputStream = thisUrl.openStream();
			BufferedReader thisReader = new BufferedReader(new InputStreamReader(thisInputStream));

			// parse lines
			String thisLine;
			for (int l = 1; (thisLine = thisReader.readLine()) != null; l++)
			{
				if (thisLine.matches("^\\s*$") || thisLine.startsWith("#"))
					continue;
				processLine(thisLine, l, thisStack);
			}

			// graph
			List<INode> theseChildren = thisRootNode.getChildren();
			if (theseChildren.size() == 1)
			{
				thisRootNode = (MutableNode) theseChildren.get(0);
				thisRootNode.setParent(null);
			}
			return new Tree(thisRootNode, null);
		}
		catch (Exception ex)
		{
			System.err.println("Text graph parser: " + ex.toString());
			return null;
		}
	}

	/**
	 * Process line
	 * 
	 * @param thisLine
	 *        line
	 * @param thisLineNumber
	 *        line number
	 * @param thisStack
	 *        node stack
	 */
	private void processLine(String thisLine, int thisLineNumber, Deque<StackEntry> thisStack)
	{
		int thisLevel = getLevel(thisLine);
		if (thisLevel == -1)
			return;

		// stack
		StackEntry thisEntry = thisStack.peek();
		int thatLevel = thisEntry.theLevel;

		// parent
		MutableNode thisParent = null;
		if (thisLevel == thatLevel)
		{
			thisStack.pop();
			thisParent = thisStack.peek().theNode;
		}
		else if (thisLevel > thatLevel)
		{
			thisParent = thisStack.peek().theNode;
		}
		else
		{
			while (thisLevel <= thatLevel)
			{
				thisStack.pop();
				thatLevel = thisStack.peek().theLevel;
			}
			thisParent = thisStack.peek().theNode;
		}

		// parse
		// label:id:backcolor:forecolor:img:link:comment
		String[] theseFields = thisLine.substring(thisLevel).split(":");
		String thisId = theseFields.length > 1 ? theseFields[1] : "N" + thisLineNumber;

		// new node
		MutableNode thisNode = new MutableNode(thisParent, thisId);
		thisStack.push(new StackEntry(thisNode, thisLevel));

		// node data
		thisNode.setLabel(theseFields[0]);
		if (theseFields.length > 2)
			thisNode.setBackColor(Utils.stringToColor(theseFields[2]));
		if (theseFields.length > 3)
			thisNode.setForeColor(Utils.stringToColor(theseFields[3]));
		if (theseFields.length > 4)
			thisNode.setImageFile(theseFields[4]);
		if (theseFields.length > 5)
			thisNode.setLink(theseFields[5]);
		if (theseFields.length > 6)
			thisNode.setContent(theseFields[6]);
	}

	/**
	 * Get level (as per indentation)
	 * 
	 * @param thisLine
	 *        line
	 * @return level
	 */
	private int getLevel(String thisLine)
	{
		for (int i = 0; i < thisLine.length(); i++)
		{
			char thisChar = thisLine.charAt(i);
			if (thisChar != ' ' && thisChar != '\t')
				return i;
		}
		return -1;
	}
}
